/**
 * Author: Max E. Kramer, Jackin (qin.zhang@uni.lu)
 * Date: 2013
 * (c) 2013 University of Luxembourg - Interdisciplinary Centre for Security Reliability and Trust (SnT)
 * All rights reserved
 * GPLv3 to be found at: http://www.gnu.org/licenses/quick-guide-gplv3.html
 */
package lu.uni.snt.geko2.weaver.kernel;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import lu.uni.geko.util.bridges.EcoreBridge;
import lu.uni.geko.util.bridges.JavaBridge;

import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EStructuralFeature.Setting;

/**
 * The class is responsible for fundamental EObject-merging functions during the weaving process.
 * These fundamental functions are not extensible. To use this class, a wrapper is needed to
 * implement other necessary requirements for a complete merger.
 * 
 * @author Max E. Kramer (initial programmer), Jackin (rewriter)
 */
public final class MergerKernel
{
	
	/**
	 * The kernel class is not extensible and only provides static functions.
	 */
	private MergerKernel()
	{

	}
	
	/**
   * Merges the given single base element with the given multiple advice elements.
   *
   * @param baseElement
   *           a base element
   * @param avElementsMapEntrySet
   *           the original set from the base to advice merge map containing the advice elements to be merged with the given base
   *           element
   */
  private void merge1BaseWithNAvElements(final EObject baseElement, final Set<EObject> avElementsMapEntrySet) {
     // copy the advice elements set as it is the real entry in the map and should not be changed accidentally
     Set<EObject> avElements = new HashSet<EObject>(avElementsMapEntrySet);
     EObject directlyMergedAvEObject = JavaBridge.pop(avElements);
     List<EObject> baseElementCopies = new ArrayList<EObject>(avElements.size());
     for (EObject avElement : avElements) {
        EObject baseElementCopy = baseCopier.copy(baseElement);
        // Now we have duplicated the base element that has to be merged with multiple advice elements.
        // This means our base2AdviceMergeBiMap should no longer map the original base element to all advice elements.
        // Instead every new duplicate of the base element should map to the corresponding advice elements (and the other way
        // round).
        // This change is realized in two steps. First we remove the mapping from the original base element to the advice
        // element.
        // Then we add a new mapping from the base duplicate to the advice element.
        avEffectuation.removeBase2AvMerge(baseElement, avElement);
        avEffectuation.addBase2AvMerge(baseElementCopy, avElement);
        // We did this mapping change as early as possible and now we can go on with processing the copy and merging it.
        baseCopier.copyReferences();
        baseElementCopies.add(baseElementCopy);
        merge1BaseWith1AvElement(baseElementCopy, avElement);
        console.println("Copied the base object '" + baseElement + "' and merged it with the advice object'" + avElement
              + "': '" + baseElementCopy + "'");
     }
     Collection<Setting> referencesToBaseElement = EcoreBridge.getReferencesTo(baseElement);
     for (Setting oldReferenceToBaseElement : referencesToBaseElement) {
        EStructuralFeature referencingFeature = oldReferenceToBaseElement.getEStructuralFeature();
        EObject oldReferencingElement = oldReferenceToBaseElement.getEObject();
        if (referencingFeature.isMany()) {
           List<EObject> referencedValues = EcoreBridge
                 .getFeatureValuesIfManyTyped(oldReferencingElement, referencingFeature);
           int currentSize = referencedValues.size();
           boolean upperBoundNotExceeded = ensureReferenceDoesNotExceedUpperBound(currentSize, referencingFeature,
                 baseElementCopies, baseElement);
           if (upperBoundNotExceeded) {
              referencedValues.addAll(baseElementCopies);
              console.println("Added reference copies for the reference to '" + baseElement + "' to the '"
                    + referencingFeature.getName() + "' feature of the base object '" + oldReferencingElement + "'");

           }
        } else { // !referencingFeature.isMany()
           EStructuralFeature referencingElementContainmentFeature = oldReferencingElement.eContainmentFeature();
           EObject referenceContainer = oldReferencingElement.eContainer();
           boolean containedInCopySource = EcoreBridge.containedIn(referenceContainer, baseElement);
           boolean containedInCopyTarget = EcoreBridge.containedIn(referenceContainer, baseElementCopies);
           if (!containedInCopySource && !containedInCopyTarget) {
              if (referencingElementContainmentFeature.isMany()) {
                 List<EObject> referencingEObjectSiblings = EcoreBridge.getFeatureValuesIfManyTyped(referenceContainer,
                       referencingElementContainmentFeature);
                 int currentSize = referencingEObjectSiblings.size();
                 boolean upperBoundNotExceeded = ensureReferenceDoesNotExceedUpperBound(currentSize,
                       referencingElementContainmentFeature, baseElementCopies, baseElement, oldReferencingElement);
                 if (upperBoundNotExceeded) {
                    for (EObject baseEObjectCopy : baseElementCopies) {
                       EObject referencingElementCopy = baseCopier.copy(oldReferencingElement);
                       baseCopier.copyReferences();
                       referencingElementCopy.eSet(referencingFeature, baseEObjectCopy);
                       referencingEObjectSiblings.add(referencingElementCopy);
                       console.println("Added the copy '" + referencingElementCopy + "' of the referencing object '"
                             + oldReferencingElement + "' for the reference '" + referencingFeature.getName()
                             + "' referencing the copy '" + baseEObjectCopy + "' of the base object '" + baseElement
                             + "' to the container '" + referenceContainer + "'");
                    }
                 }
              }
           }
        }
        // TODO MK recursively continue copying referencing objects that reference base objects that have to be copied
        // until you reach a reference that can be copied
     }
     merge1BaseWith1AvElement(baseElement, directlyMergedAvEObject);
  }
	
}
